#pragma once

#include "Property.h"

#include "Association.h"
#include "Class.h"
#include "DataType.h"
#include "Interface.h"
#include "ParameterableElement.h"
#include "RedefinableElement.h"
#include "Type.h"
#include "ValueSpecification.h"

namespace uml {

const bool Property::isComposite_default_ = false;
const bool Property::isDerived_default_ = false;
const bool Property::isDerivedUnion_default_ = false;
const bool Property::isID_default_ = false;
Property::Property(const QString& umlType)
    : StructuralFeature(umlType) {}

void Property::fromJson(const QJsonObject& json, QStringList& errorList) {
    if (json.isEmpty()) {
        return;
    }

    ConnectableElement::fromJson(json, errorList);
    StructuralFeature::fromJson(json, errorList);
    DeploymentTarget::fromJson(json, errorList);
    aggregation_ = (AggregationKind)json.value("aggregation").toInt((int)aggregation_);
    association_ = JsonSerializable::create<Association>(json.value("association"), errorList);

    associationEnd_ = JsonSerializable::create<Property>(json.value("associationEnd"), errorList);

    class2_ = JsonSerializable::create<Class>(json.value("class2"), errorList);

    datatype_ = JsonSerializable::create<DataType>(json.value("datatype"), errorList);

    defaultValue_ = JsonSerializable::create<ValueSpecification>(json.value("defaultValue"), errorList);

    interface2_ = JsonSerializable::create<Interface>(json.value("interface2"), errorList);

    isComposite_ = json.value("isComposite").toBool(isComposite_default_);
    isDerived_ = json.value("isDerived").toBool(isDerived_default_);
    isDerivedUnion_ = json.value("isDerivedUnion").toBool(isDerivedUnion_default_);
    isID_ = json.value("isID").toBool(isID_default_);
    opposite_ = JsonSerializable::create<Property>(json.value("opposite"), errorList);

    owningAssociation_ = JsonSerializable::create<Association>(json.value("owningAssociation"), errorList);

    JsonSerializable::fromJsonArray(qualifiers_, "qualifiers", json, errorList);
    JsonSerializable::fromJsonArray(redefinedPropertys_, "redefinedPropertys", json, errorList);
    JsonSerializable::fromJsonArray(subsettedPropertys_, "subsettedPropertys", json, errorList);
}

void Property::toJson(QJsonObject& json) {
    ConnectableElement::toJson(json);
    StructuralFeature::toJson(json);
    DeploymentTarget::toJson(json);
    json.insert("aggregation", (int)aggregation_);
    if (association_) {
        QJsonObject association_json;
        association_->toJson(association_json);
        json.insert("association", association_json);
    }
    if (associationEnd_) {
        QJsonObject associationEnd_json;
        associationEnd_->toJson(associationEnd_json);
        json.insert("associationEnd", associationEnd_json);
    }
    if (class2_) {
        QJsonObject class2_json;
        class2_->toJson(class2_json);
        json.insert("class2", class2_json);
    }
    if (datatype_) {
        QJsonObject datatype_json;
        datatype_->toJson(datatype_json);
        json.insert("datatype", datatype_json);
    }
    if (defaultValue_) {
        QJsonObject defaultValue_json;
        defaultValue_->toJson(defaultValue_json);
        json.insert("defaultValue", defaultValue_json);
    }
    if (interface2_) {
        QJsonObject interface2_json;
        interface2_->toJson(interface2_json);
        json.insert("interface2", interface2_json);
    }
    if (isComposite_ != isComposite_default_) {
        json.insert("isComposite", isComposite_);
    }
    if (isDerived_ != isDerived_default_) {
        json.insert("isDerived", isDerived_);
    }
    if (isDerivedUnion_ != isDerivedUnion_default_) {
        json.insert("isDerivedUnion", isDerivedUnion_);
    }
    if (isID_ != isID_default_) {
        json.insert("isID", isID_);
    }
    if (opposite_) {
        QJsonObject opposite_json;
        opposite_->toJson(opposite_json);
        json.insert("opposite", opposite_json);
    }
    if (owningAssociation_) {
        QJsonObject owningAssociation_json;
        owningAssociation_->toJson(owningAssociation_json);
        json.insert("owningAssociation", owningAssociation_json);
    }
    JsonSerializable::toJsonArray(qualifiers_, "qualifiers", json);
    JsonSerializable::toJsonArray(redefinedPropertys_, "redefinedPropertys", json);
    JsonSerializable::toJsonArray(subsettedPropertys_, "subsettedPropertys", json);
}

/// <summary>
/// isAttribute
    /// The query isAttribute() is true if the Property is defined as an attribute of
    /// some Classifier.
/// </summary>
    /// <code>
    /// result = (not classifier->isEmpty())
    /// </code>
bool Property::isAttribute() const
{
    return false;
}

/// <summary>
/// isCompatibleWith
    /// The query isCompatibleWith() determines if this Property is compatible with the
    /// specified ParameterableElement. This Property is compatible with
    /// ParameterableElement p if the kind of this Property is thesame as or a subtype
    /// of the kind of p. Further, if p is a TypedElement, then the type of this
    /// Property must be conformant with the type of p.
/// </summary>
    /// <code>
    /// result = (self.oclIsKindOf(p.oclType()) and (p.oclIsKindOf(TypeElement) implies
    /// self.type.conformsTo(p.oclAsType(TypedElement).type)))
    /// </code>
bool Property::isCompatibleWith(shared_ptr<ParameterableElement> p) const
{
    return false;
}

/// <summary>
/// isConsistentWith
    /// The query isConsistentWith() specifies, for any two Properties in a context in
    /// which redefinition is possible, whether redefinition would be logically
    /// consistent. A redefining Property is consistent with a redefined Property if the
    /// type of the redefining Property conforms to the type of the redefined Property,
    /// and the multiplicity of the redefining Property (if specified) is contained in
    /// the multiplicity of the redefined Property.
/// </summary>
    /// <code>
    /// redefiningElement.isRedefinitionContextValid(self)
    /// </code>
    /// <code>
    /// result = (redefiningElement.oclIsKindOf(Property) and 
    ///   let prop : Property = redefiningElement.oclAsType(Property) in 
    ///   (prop.type.conformsTo(self.type) and 
    ///   ((prop.lowerBound()->notEmpty() and self.lowerBound()->notEmpty()) implies prop.lowerBound() >= self.lowerBound()) and 
    ///   ((prop.upperBound()->notEmpty() and self.upperBound()->notEmpty()) implies prop.lowerBound() <= self.lowerBound()) and 
    ///   (self.isComposite implies prop.isComposite)))
    /// </code>
bool Property::isConsistentWith(shared_ptr<RedefinableElement> redefiningElement) const
{
    return false;
}

/// <summary>
/// isNavigable
    /// The query isNavigable() indicates whether it is possible to navigate across the
    /// property.
/// </summary>
    /// <code>
    /// result = (not classifier->isEmpty() or association.navigableOwnedEnd->includes(self))
    /// </code>
bool Property::isNavigable() const
{
    return false;
}

/// <summary>
/// subsettingContexts
    /// The query subsettingContext() gives the context for subsetting a Property. It
    /// consists, in the case of an attribute, of the corresponding Classifier, and in
    /// the case of an association end, all of the Classifiers at the other ends.
/// </summary>
    /// <code>
    /// result = (if association <> null
    /// then association.memberEnd->excluding(self)->collect(type)->asSet()
    /// else 
    ///   if classifier<>null
    ///   then classifier->asSet()
    ///   else Set{} 
    ///   endif
    /// endif)
    /// </code>
shared_ptr<vector<Type>> Property::subsettingContexts() const
{
    return nullptr;
}

/// <summary>
/// aggregation getter
/// Specifies the kind of aggregation that applies to the Property.
/// </summary>
AggregationKind Property::aggregation() const {
    return aggregation_;
}

/// <summary>
/// aggregation setter
/// Specifies the kind of aggregation that applies to the Property.
/// </summary>
void Property::setAggregation(AggregationKind value) {
    aggregation_ = value;
}

/// <summary>
/// association getter
/// The Association of which this Property is a member, if any.
/// </summary>
shared_ptr<Association> Property::association() const {
    return association_;
}

/// <summary>
/// association setter
/// The Association of which this Property is a member, if any.
/// </summary>
void Property::setAssociation(shared_ptr<Association> value) {
    association_ = value;
}

/// <summary>
/// associationEnd getter
/// Designates the optional association end that owns a qualifier attribute.
/// </summary>
shared_ptr<Property> Property::associationEnd() const {
    return associationEnd_;
}

/// <summary>
/// associationEnd setter
/// Designates the optional association end that owns a qualifier attribute.
/// </summary>
void Property::setAssociationEnd(shared_ptr<Property> value) {
    associationEnd_ = value;
}

/// <summary>
/// class2 getter
/// The Class that owns this Property, if any.
/// </summary>
shared_ptr<Class> Property::class2() const {
    return class2_;
}

/// <summary>
/// class2 setter
/// The Class that owns this Property, if any.
/// </summary>
void Property::setClass2(shared_ptr<Class> value) {
    class2_ = value;
}

/// <summary>
/// datatype getter
/// The DataType that owns this Property, if any.
/// </summary>
shared_ptr<DataType> Property::datatype() const {
    return datatype_;
}

/// <summary>
/// datatype setter
/// The DataType that owns this Property, if any.
/// </summary>
void Property::setDatatype(shared_ptr<DataType> value) {
    datatype_ = value;
}

/// <summary>
/// defaultValue getter
/// A ValueSpecification that is evaluated to give a default value for the Property
/// when an instance of the owning Classifier is instantiated.
/// </summary>
shared_ptr<ValueSpecification> Property::defaultValue() const {
    return defaultValue_;
}

/// <summary>
/// defaultValue setter
/// A ValueSpecification that is evaluated to give a default value for the Property
/// when an instance of the owning Classifier is instantiated.
/// </summary>
void Property::setDefaultValue(shared_ptr<ValueSpecification> value) {
    defaultValue_ = value;
}

/// <summary>
/// interface2 getter
/// The Interface that owns this Property, if any.
/// </summary>
shared_ptr<Interface> Property::interface2() const {
    return interface2_;
}

/// <summary>
/// interface2 setter
/// The Interface that owns this Property, if any.
/// </summary>
void Property::setInterface2(shared_ptr<Interface> value) {
    interface2_ = value;
}

/// <summary>
/// isComposite getter
/// If isComposite is true, the object containing the attribute is a container for
/// the object or value contained in the attribute. This is a derived value,
/// indicating whether the aggregation of the Property is composite or not.
/// </summary>
bool Property::isComposite() const {
    return isComposite_;
}

/// <summary>
/// isComposite setter
/// If isComposite is true, the object containing the attribute is a container for
/// the object or value contained in the attribute. This is a derived value,
/// indicating whether the aggregation of the Property is composite or not.
/// </summary>
void Property::setIsComposite(bool value) {
    isComposite_ = value;
}

/// <summary>
/// isDerived getter
/// Specifies whether the Property is derived, i.e., whether its value or values can
/// be computed from other information.
/// </summary>
bool Property::isDerived() const {
    return isDerived_;
}

/// <summary>
/// isDerived setter
/// Specifies whether the Property is derived, i.e., whether its value or values can
/// be computed from other information.
/// </summary>
void Property::setIsDerived(bool value) {
    isDerived_ = value;
}

/// <summary>
/// isDerivedUnion getter
/// Specifies whether the property is derived as the union of all of the Properties
/// that are constrained to subset it.
/// </summary>
bool Property::isDerivedUnion() const {
    return isDerivedUnion_;
}

/// <summary>
/// isDerivedUnion setter
/// Specifies whether the property is derived as the union of all of the Properties
/// that are constrained to subset it.
/// </summary>
void Property::setIsDerivedUnion(bool value) {
    isDerivedUnion_ = value;
}

/// <summary>
/// isID getter
/// True indicates this property can be used to uniquely identify an instance of the
/// containing Class.
/// </summary>
bool Property::isID() const {
    return isID_;
}

/// <summary>
/// isID setter
/// True indicates this property can be used to uniquely identify an instance of the
/// containing Class.
/// </summary>
void Property::setIsID(bool value) {
    isID_ = value;
}

/// <summary>
/// opposite getter
/// In the case where the Property is one end of a binary association this gives the
/// other end.
/// </summary>
shared_ptr<Property> Property::opposite() const {
    return opposite_;
}

/// <summary>
/// opposite setter
/// In the case where the Property is one end of a binary association this gives the
/// other end.
/// </summary>
void Property::setOpposite(shared_ptr<Property> value) {
    opposite_ = value;
}

/// <summary>
/// owningAssociation getter
/// The owning association of this property, if any.
/// </summary>
shared_ptr<Association> Property::owningAssociation() const {
    return owningAssociation_;
}

/// <summary>
/// owningAssociation setter
/// The owning association of this property, if any.
/// </summary>
void Property::setOwningAssociation(shared_ptr<Association> value) {
    owningAssociation_ = value;
}

/// <summary>
/// qualifiers getter
/// An optional list of ordered qualifier attributes for the end.
/// </summary>
vector<shared_ptr<Property>>& Property::qualifiers() {
    return qualifiers_;
}

/// <summary>
/// redefinedPropertys getter
/// The properties that are redefined by this property, if any.
/// </summary>
vector<shared_ptr<Property>>& Property::redefinedPropertys() {
    return redefinedPropertys_;
}

/// <summary>
/// subsettedPropertys getter
/// The properties of which this Property is constrained to be a subset, if any.
/// </summary>
vector<shared_ptr<Property>>& Property::subsettedPropertys() {
    return subsettedPropertys_;
}

} // namespace uml
